package gemini.basic.manager.impl;

import gemini.basic.dao.BillDao;
import gemini.basic.dao.BillDetailDao;
import gemini.basic.dao.CartDao;
import gemini.basic.dao.CartStatusDao;
import gemini.basic.dao.DiscountScaleDao;
import gemini.basic.dao.DistributorDao;
import gemini.basic.dao.MonthDao;
import gemini.basic.dao.MonthlyPointDao;
import gemini.basic.dao.OrderDao;
import gemini.basic.dao.OrderStatusDao;
import gemini.basic.dao.PaymentDao;
import gemini.basic.dao.PersonDao;
import gemini.basic.dao.ProductDao;
import gemini.basic.manager.ShoppingManager;
import gemini.basic.model.Bill;
import gemini.basic.model.BillDetail;
import gemini.basic.model.Cart;
import gemini.basic.model.CartStatus;
import gemini.basic.model.Distributor;
import gemini.basic.model.Month;
import gemini.basic.model.MonthlyPoint;
import gemini.basic.model.Order;
import gemini.basic.model.OrderStatus;
import gemini.basic.model.Payment;
import gemini.basic.model.Person;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ShoppingManagerImpl implements ShoppingManager {

	private final Logger logger = LoggerFactory
			.getLogger(ShoppingManager.class);

	private CartDao cartDao;

	private CartStatusDao cartStatusDao;

	private PersonDao personDao;

	private ProductDao productDao;

	private PaymentDao paymentDao;

	private DistributorDao distributorDao;

	private MonthDao monthDao;

	private MonthlyPointDao monthlyPointDao;

	private BillDao billDao;

	private BillDetailDao billDetailDao;

	private DiscountScaleDao discountScaleDao;

	private OrderStatusDao orderStatusDao;

	private OrderDao orderDao;

	public CartStatusDao getCartStatusDao() {
		return cartStatusDao;
	}

	public void setCartStatusDao(CartStatusDao cartStatusDao) {
		this.cartStatusDao = cartStatusDao;
	}

	public OrderStatusDao getOrderStatusDao() {
		return orderStatusDao;
	}

	public void setOrderStatusDao(OrderStatusDao orderStatusDao) {
		this.orderStatusDao = orderStatusDao;
	}

	public void setMonthDao(MonthDao monthDao) {
		this.monthDao = monthDao;
	}

	public MonthDao getMonthDao() {
		return monthDao;
	}

	public void setMonthlyPointDao(MonthlyPointDao monthlyPointDao) {
		this.monthlyPointDao = monthlyPointDao;
	}

	public MonthlyPointDao getMonthlyPointDao() {
		return monthlyPointDao;
	}

	public DistributorDao getDistributorDao() {
		return distributorDao;
	}

	public void setDistributorDao(DistributorDao distributorDao) {
		this.distributorDao = distributorDao;
	}

	public void setProductDao(ProductDao productDao) {
		this.productDao = productDao;
	}

	public void setPersonDao(PersonDao personDao) {
		this.personDao = personDao;
	}

	public void setCartDao(CartDao cartDao) {
		this.cartDao = cartDao;
	}

	public void setOrderDao(OrderDao orderDao) {
		this.orderDao = orderDao;
	}

	public void setBillDao(BillDao billDao) {
		this.billDao = billDao;
	}

	public BillDao getBillDao() {
		return billDao;
	}

	public void setBillDetailDao(BillDetailDao billDetailDao) {
		this.billDetailDao = billDetailDao;
	}

	public BillDetailDao getBillDetailDao() {
		return billDetailDao;
	}

	public void setDiscountScaleDao(DiscountScaleDao discountScaleDao) {
		this.discountScaleDao = discountScaleDao;
	}

	public DiscountScaleDao getDiscountScaleDao() {
		return discountScaleDao;
	}

	@Override
	public Cart createCart(Person person) {
		logger.debug("============== create new cart");

		Cart cart = new Cart();

		person = personDao.getById(person.getId());

		cart.setAddressByBillingAddress(person.getAddress());
		cart.setAddressByShippingAddress(person.getAddress());
		cart.setDistributor(person.getDistributor());

		cart.setProcessingDate(new Date());

		logger.debug("============== create INI status");
		cart.setCartStatuses(new ArrayList<CartStatus>(1));

		CartStatus status = new CartStatus();
		status.setCart(cart);
		status.setCode("INI");
		status.setValidFrom(new Date());

		cart.getCartStatuses().add(status);

		Cart result = cartDao.saveOrUpdate(cart, true);

		return result;
	}

	@Override
	public Order saveOrder(Order order) {
		logger.debug("============== save order");

		logger.debug("============== create INI status");
		order.setOrderStatuses(new ArrayList<OrderStatus>(1));

		OrderStatus status = new OrderStatus();
		status.setOrder(order);
		status.setCode("INI");
		status.setValidFrom(new Date());

		order.getOrderStatuses().add(status);

		Order result = orderDao.saveOrUpdate(order, true);

		return result;

	}

	@Override
	public Order updateOrder(Order order) {
		logger.debug("============== update order");
		Order dbOrder = orderDao.getById(order.getId());
		dbOrder.setQuantity(order.getQuantity());

		Order result = orderDao.saveOrUpdate(dbOrder, true);

		return result;

	}

	@Override
	public void deleteOrder(Order order) {
		logger.debug("============== delete order");
		orderDao.delete(orderDao.getById(order.getId()));
	}

	@Override
	public Cart getOpenCartByDistributorId(int distributorId) {
		Cart result = cartDao.getOpenCartByDistributorId(distributorId);
		return result;

	}

	@Override
	public Integer completeCart(int cartId, Payment payment) {
		Cart cart = cartDao.getById(cartId);

		logger.debug("============== update valid to date of last status");
		for (CartStatus status : cart.getCartStatuses()) {
			if (status.getValidTo() == null) {
				status.setValidTo(new Date());
				break;
			}
		}

		logger.debug("============== add new status ");
		CartStatus cartStatus = new CartStatus();
		cartStatus.setCode("FIN");
		cartStatus.setCart(cart);
		cartStatus.setValidFrom(new Date());

		cart.getCartStatuses().add(cartStatus);

		logger.debug("============== save payment");
		Payment resultPayment = paymentDao.saveOrUpdate(payment, true);
		cart.setPayment(resultPayment);
		logger.debug("============== update to db");
		Cart result = cartDao.saveOrUpdate(cart, true);
		return result.getId();
	}

	@Override
	public List<Order> saveOrUpdateOrders(List<Order> orders) {
		List<Order> resultList = new ArrayList<Order>();
		for (Order order : orders) {
			Order result;
			if (order.getId() == null) {
				order.setProduct(productDao.getByCode(order.getProduct()
						.getCode()));
				result = saveOrder(order);
			} else {
				result = updateOrder(order);
			}
			resultList.add(result);
		}
		return resultList;
	}

	@Override
	public void deleteOrders(List<Order> orders) {
		for (Order order : orders) {
			deleteOrder(order);
		}
	}

	@Override
	public int findAndDeleteUnusedDatabase() {
		List<Order> deletedOrders = orderDao.findUnusedOrders();

		int numberUnusedOrders = deletedOrders == null ? 0 : deletedOrders
				.size();
		
		logger.info("=======================Number of unused orders:{}", numberUnusedOrders);

		if (numberUnusedOrders > 0) {
			logger.info("=======================Deleting unused orders ... =======================");
			for (Order order : deletedOrders) {

				for (OrderStatus dStt : order.getOrderStatuses()) {
					orderStatusDao.deleteOrderStatus(dStt);
				}

				orderDao.delete(order);
			}
			logger.info("=======================Deleting unused orders ... complete! =======================");
		}

		List<Cart> deletedCarts = cartDao.findUnusedCarts();
		
		int numberUnusedCarts = deletedCarts == null ? 0 : deletedCarts
				.size();

		logger.info("=======================Number of unused carts:{}",
				numberUnusedCarts);

		if (numberUnusedCarts > 0) {
			logger.info("=======================Deleting unused carts ... =======================");
			for (Cart cart : deletedCarts) {
				for (CartStatus dStt : cart.getCartStatuses()) {
					cartStatusDao.deleteCartStatus(dStt);
				}
				cartDao.deleteCart(cart);
			}
			logger.info("=======================Deleting unused carts ... complete! =======================");
		}

		int result = numberUnusedOrders + numberUnusedCarts;

		return result;
	}

	public void setPaymentDao(PaymentDao paymentDao) {
		this.paymentDao = paymentDao;
	}

	public PaymentDao getPaymentDao() {
		return paymentDao;
	}

	@Override
	public int checkPayment() {
		List<Payment> uncompletePayments = paymentDao
				.getAllUncompletePayments();
		
		int numberUncompletedPayments = uncompletePayments == null ? 0:uncompletePayments.size();
		logger.info(
				"=======================Number of uncompleted payments : {}",
				numberUncompletedPayments);
		if (numberUncompletedPayments > 0) {
			logger.info("=======================Processing uncompleted payments ... =======================");
			Date date = new Date();
			
			for (Payment payment : uncompletePayments) {
				payment.setDateOfComplete(date);
				paymentDao.saveOrUpdate(payment, true);
			}
			logger.info("=======================Processing uncompleted payments ... finished! =======================");
		}
		return numberUncompletedPayments;
	}

	@Override
	public int processCart() {
		List<Cart> finishedcarts = cartDao.findFinishedCarts();
		
		int numberFinishedCarts = finishedcarts == null ? 0:finishedcarts.size();
		
		logger.info("=======================Number of finished carts:{}",
				numberFinishedCarts);

		if (numberFinishedCarts > 0) {
			for (Cart cart : finishedcarts) {
				logger.info("=======================Processing cart id <{}> ... =======================", cart.getId());
				Distributor distributor = distributorDao.getByCode(cart
						.getDistributor().getCode());

				int idOfCurrentMonthlyPoint = getIdOfCurrentMonthlyPoint(distributor
						.getMonthlyPoints());
				
				MonthlyPoint monthlyPoint = null;
				
				if (idOfCurrentMonthlyPoint >= 0) {
					monthlyPoint = monthlyPointDao
							.getById(idOfCurrentMonthlyPoint);
					int currentPoint = monthlyPoint.getPersonalPoint();
					monthlyPoint
							.setPersonalPoint(currentPoint + sumPoint(cart));
				} else {
					monthlyPoint = createCurrentMonthlyPoint(distributor, cart);
				}

				logger.info("=======================Updating volumn point for distributor id <{}> ...", distributor.getId());
				monthlyPoint = monthlyPointDao.saveOrUpdate(monthlyPoint, true);
				logger.info("=======================Updating volumn point for distributor id <{}> ... finished!", distributor.getId());

				changeStatusAndCreateBill(cart);

				logger.info("=======================Processing cart id <{}> ... finished! =======================", cart.getId());
			}
		}
		return numberFinishedCarts;
	}

	private void changeStatusAndCreateBill(Cart cart) {
		
		logger.info("=======================Creating bill ...");
		Bill bill = new Bill();
		bill = createBillFromCart(cart);
		bill = billDao.saveOrUpdate(bill, true);
		logger.info("=======================Creating bill ... finished!");
		
		List<Order> orders = cart.getOrders();
		
		logger.info("=======================Creating bill details ...");
		for (Order order : orders) {
			BillDetail billDetail = new BillDetail();
			billDetail = createBillDetailFromOrder(bill, order);
			billDetailDao.saveOrUpdate(billDetail, true);
		}
		logger.info("=======================Creating bill details ... finished!");

		logger.info("=======================Updating order status to UNUSED ...");
		for (Order order : orders) {
			for (OrderStatus orderStatus : order.getOrderStatuses()) {
				orderStatus.setCode("UNU");
			}
			orderDao.saveOrUpdate(order, true);
		}
		logger.info("=======================Updating order status to UNUSED ... finished!");
		
		logger.info("=======================Updating cart status to UNUSED ...");
		for (CartStatus cartStatus : cart.getCartStatuses()) {
			cartStatus.setCode("UNU");
		}
		cartDao.saveOrUpdate(cart, true);
		logger.info("=======================Updating cart status to UNUSED ... finished!");
	}

	private BillDetail createBillDetailFromOrder(Bill bill, Order order) {
		BillDetail billDetail = new BillDetail();
		billDetail.setBill(bill);
		billDetail.setProduct(order.getProduct());
		billDetail.setQuantity(order.getQuantity());
		return billDetail;
	}

	private Bill createBillFromCart(Cart cart) {
		Bill bill = new Bill();
		bill.setAddressByBillingAddressId(cart.getAddressByBillingAddress());
		bill.setAddressByShippingAddressId(cart.getAddressByShippingAddress());
		bill.setDiscountScaleValue(cart.getDistributor().getDiscountScale());
		bill.setDistributorCode(cart.getDistributor().getCode());
		bill.setProcessingDate(new Date());
		bill.setPurchasedBy(cart.getDistributor().getPersons().get(0)
				.getLastName());
		bill.setPayment(cart.getPayment());
		return bill;
	}

	private int sumPoint(Cart cart) {
		int point = 0;
		if (!cart.getOrders().isEmpty() && cart.getOrders() != null) {
			for (Order order : cart.getOrders()) {
				point += order.getQuantity()
						* order.getProduct().getVolumePoint();
			}
		}
		return point;
	}

	@SuppressWarnings("deprecation")
	private int getIdOfCurrentMonthlyPoint(List<MonthlyPoint> monthlyPoints) {
		int id = -1;
		if (monthlyPoints != null) {
			Date date = new Date();
			int currentMonth = date.getMonth() + 1;
			int currentYear = date.getYear() + 1900;

			for (MonthlyPoint point : monthlyPoints) {
				if (point.getMonth().getMonth() == currentMonth
						&& point.getMonth().getYear() == currentYear) {
					id = point.getId();
					break;
				}
			}

		}
		return id;
	}

	@SuppressWarnings("deprecation")
	private int getEndDayByMonth(Date date) {
		switch (date.getMonth() + 1) {
		case 1:
		case 3:
		case 5:
		case 7:
		case 8:
		case 10:
		case 12:
			return 31;
		case 4:
		case 6:
		case 9:
		case 11:
			return 30;
		default:
			if ((date.getYear() + 1900) % 4 == 0)
				return 29;
			else
				return 28;
		}
	}

	@SuppressWarnings("deprecation")
	private MonthlyPoint createCurrentMonthlyPoint(Distributor distributor,
			Cart cart) {
		Date currentDate = new Date();
		Month month = new Month();
		month.setMonth(currentDate.getMonth() + 1);
		month.setYear(currentDate.getYear() + 1900);
		month.setStartDate(1);
		month.setEndDate(getEndDayByMonth(currentDate));

		month = monthDao.saveOrUpdate(month, true);
		logger.info("Create current month {}-{} success!", month.getMonth(),
				month.getYear());

		MonthlyPoint monthlyPoint = new MonthlyPoint();
		monthlyPoint.setMonth(month);
		monthlyPoint.setDistributor(distributor);
		monthlyPoint.setPersonalPoint(sumPoint(cart));

		return monthlyPoint;
	}

	@Override
	public List<Bill> getBillByDistributorCode(String id) {
		logger.debug("============== get bill by id <{}>", id);
		List<Bill> result = billDao.getBillByDistributorCode(id);
		return result;
	}

	@Override
	public void checkOut(Payment payment) {
		payment.setDistributor(distributorDao.getByCode(payment
				.getDistributor().getCode()));
		paymentDao.saveOrUpdate(payment, true);
	}

}
